package com.gitee.fastmybatis.core;

import com.gitee.fastmybatis.core.ext.SqlSessionFactoryBuilderContext;
import com.gitee.fastmybatis.core.ext.SqlSessionFactoryBuilderExt;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;

import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Properties;

/**
 * @author thc
 */
public class Fastmybatis {

    protected SqlSessionFactory sqlSessionFactory;

    protected String configLocation;

    protected String[] mapperLocations;

    protected String basePackage;

    protected Environment environment;

    protected String environmentId;

    protected Properties properties;

    protected FastmybatisConfig config = new FastmybatisConfig();

    protected String dialect;

    private DataSource dataSource;

    private List<Class<?>> mapperClassList;

    private Class<?> bootClass;

    private List<Interceptor> plugins;

    /**
     * 创建Fastmybatis对象
     *
     * @return 返回Fastmybatis对象
     */
    public static Fastmybatis create() {
        return new Fastmybatis();
    }

    /**
     * 创建Fastmybatis对象
     *
     * @return 返回Fastmybatis对象
     */
    public static Fastmybatis create(Class<?> bootClass) {
        return new Fastmybatis(bootClass);
    }

    public Fastmybatis() {
    }

    public Fastmybatis(Class<?> bootClass) {
        this.bootClass = bootClass;
    }

    /**
     * 指定mybatis xml文件classpath目录，如：mybatis/mapper/XXX.xml,则指定：<code>mybatis/mapper</code><br>
     * <p>
     * 一旦指定了这个参数，<code>mybatis-config.xml</code>文件不用配置{@literal <mapper></mapper>}节点，系统会自动加载xml文件
     * </p>
     *
     * @param mapperLocations mybatis xml文件classpath目录
     * @return 返回Fastmybatis对象
     */
    public Fastmybatis mapperLocations(String... mapperLocations) {
        this.mapperLocations = mapperLocations;
        return this;
    }

    /**
     * mybatis-config.xml文件classpath路径，如：mybatis/mybatis-config.xml
     *
     * @param configLocation mybatis-config.xml文件classpath路径
     * @return 返回Fastmybatis对象
     */
    public Fastmybatis configLocation(String configLocation) {
        this.configLocation = configLocation;
        return this;
    }

    /**
     * 扫描mapper所在的package，多个使用英文逗号（,）隔开。尽量指定到mapper所在的package，减少扫描时间，如：com.xx.xx.mapper
     *  <pre>
     *  <b>注意:</b>
     *  如果不指定，则必须使用 {@link #create(Class)} 进行创建
     *  </pre>
     * @param basePackage mapper所在的package
     * @return 返回Fastmybatis对象
     */
    public Fastmybatis basePackage(String basePackage) {
        this.basePackage = basePackage;
        return this;
    }

    /**
     * 设置配置项
     *
     * @param config 配置类
     * @return 返回Fastmybatis对象
     */
    public Fastmybatis config(FastmybatisConfig config) {
        this.config = config;
        return this;
    }

    /**
     * 指定环境名称。不指定默认为{@literal <environment></environment>}的id属性
     *
     * @param environment 环境名称
     * @return 返回Fastmybatis对象
     */
    public Fastmybatis environment(Environment environment) {
        this.environment = environment;
        return this;
    }

    /**
     * 指定环境名称。不指定默认为{@literal <environment></environment>}的id属性
     *
     * @param environmentId 环境名称
     * @return 返回Fastmybatis对象
     */
    public Fastmybatis environmentId(String environmentId) {
        this.environmentId = environmentId;
        return this;
    }

    /**
     * 指定额外属性
     *
     * @param properties 额外属性
     * @return 返回Fastmybatis对象
     */
    public Fastmybatis properties(Properties properties) {
        this.properties = properties;
        return this;
    }

    /**
     * 设置数据库方言
     *
     * @param dialect 如：mysql
     * @return 返回Fastmybatis对象
     */
    public Fastmybatis dialect(String dialect) {
        this.dialect = Objects.requireNonNull(dialect);
        return this;
    }

    public Fastmybatis dataSource(DataSource dataSource) {
        this.dataSource = dataSource;
        return this;
    }

    public Fastmybatis addMapper(Class<?> mapperClass) {
        if (this.mapperClassList == null) {
            this.mapperClassList = new ArrayList<>(8);
        }
        this.mapperClassList.add(mapperClass);
        return this;
    }

    public Fastmybatis plugins(Interceptor... interceptors) {
        if (plugins == null) {
            plugins = new ArrayList<>();
        }
        plugins.addAll(Arrays.asList(interceptors));
        return this;
    }

    public Fastmybatis load() {
        Objects.requireNonNull(config);
        if (basePackage == null && bootClass != null) {
            basePackage = bootClass.getPackage().getName();
        }
        if (mapperClassList == null && basePackage == null) {
            throw new RuntimeException("未指定 mapperClass 或 basePackage");
        }
        if (dataSource != null) {
            MybatisLoader mybatisLoader = new MybatisLoader(config, dataSource);
            mybatisLoader.setMapperClassList(mapperClassList);
            mybatisLoader.setBasePackage(basePackage);
            mybatisLoader.setMapperLocations(mapperLocations);
            mybatisLoader.setPlugins(plugins);
            mybatisLoader.load();
            SqlSessionFactoryBuilderContext sqlSessionFactoryBuilderContext = new SqlSessionFactoryBuilderContext(
                    mybatisLoader.getMapperLocationsBuilder(),
                    mybatisLoader.getSqlSessionFactoryBuilder(),
                    mybatisLoader.getConfiguration()
            );
            this.sqlSessionFactory = sqlSessionFactoryBuilderContext.getSqlSessionFactory();
        } else {
            Objects.requireNonNull(basePackage);
            this.config.setMapperLocations(mapperLocations);
            try {
                InputStream inputStream = Resources.getResourceAsStream(configLocation);
                SqlSessionFactoryBuilderExt sqlSessionFactoryBuilderExt = new SqlSessionFactoryBuilderExt(basePackage, config, dialect, environment);
                SqlSessionFactoryBuilderContext sqlSessionFactoryBuilderContext = sqlSessionFactoryBuilderExt.buildSqlSessionFactoryBuilderContext(inputStream, environmentId, properties);
                this.sqlSessionFactory = sqlSessionFactoryBuilderContext.getSqlSessionFactory();
            } catch (IOException e) {
                throw new RuntimeException("初始化mybatis失败", e);
            }
        }

        return this;
    }

    public SqlSessionFactory getSqlSessionFactory() {
        return sqlSessionFactory;
    }

    @Override
    public String toString() {
        return "Fastmybatis{" +
                "configLocation='" + configLocation + '\'' +
                ", mapperLocations=" + Arrays.toString(mapperLocations) +
                ", basePackage='" + basePackage + '\'' +
                '}';
    }
}
